/* 
 * Gneat (http://gneat.org)
 *
 * (C) Copyright 2011 and beyond - Claudio Stella, all rights reserved.
 *
 * This file is part of Gneat.
 *
 * Gneat is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License (LGPL)
 * as published by the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Gneat is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * and the GNU General Public License, for completeness, along with Gneat.  
 * If not, see <http://www.gnu.org/licenses/>.
 */
package org.neatshell.gneat.client.presenter.core;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;

import org.neatshell.gneat.client.GneatCore;
import org.neatshell.gneat.client.callback.CallbackPresenter;
import org.neatshell.gneat.client.callback.CallbackString;
import org.neatshell.gneat.client.event.IBlurEvent;
import org.neatshell.gneat.client.event.IChangeEvent;
import org.neatshell.gneat.client.event.IErrorEvent;
import org.neatshell.gneat.client.event.IFocusEvent;
import org.neatshell.gneat.client.event.ILoadEvent;
import org.neatshell.gneat.client.event.IScrollEvent;
import org.neatshell.gneat.client.event.ISubmitEvent;
import org.neatshell.gneat.client.event.keyboard.IKeyDownEvent;
import org.neatshell.gneat.client.event.keyboard.IKeyPressEvent;
import org.neatshell.gneat.client.event.keyboard.IKeyUpEvent;
import org.neatshell.gneat.client.event.mouse.IClickEvent;
import org.neatshell.gneat.client.event.mouse.IDblClickEvent;
import org.neatshell.gneat.client.event.mouse.IMouseDownEvent;
import org.neatshell.gneat.client.event.mouse.IMouseMoveEvent;
import org.neatshell.gneat.client.event.mouse.IMouseOutEvent;
import org.neatshell.gneat.client.event.mouse.IMouseOverEvent;
import org.neatshell.gneat.client.event.mouse.IMouseUpEvent;
import org.neatshell.gneat.client.handler.BlurHandler;
import org.neatshell.gneat.client.handler.ChangeHandler;
import org.neatshell.gneat.client.handler.CustomHandler;
import org.neatshell.gneat.client.handler.ErrorHandler;
import org.neatshell.gneat.client.handler.FocusHandler;
import org.neatshell.gneat.client.handler.Handler;
import org.neatshell.gneat.client.handler.LoadHandler;
import org.neatshell.gneat.client.handler.ScrollHandler;
import org.neatshell.gneat.client.handler.SubmitHandler;
import org.neatshell.gneat.client.handler.keyboard.KeyDownHandler;
import org.neatshell.gneat.client.handler.keyboard.KeyPressHandler;
import org.neatshell.gneat.client.handler.keyboard.KeyUpHandler;
import org.neatshell.gneat.client.handler.mouse.ClickHandler;
import org.neatshell.gneat.client.handler.mouse.DblClickHandler;
import org.neatshell.gneat.client.handler.mouse.MouseDownHandler;
import org.neatshell.gneat.client.handler.mouse.MouseMoveHandler;
import org.neatshell.gneat.client.handler.mouse.MouseOutHandler;
import org.neatshell.gneat.client.handler.mouse.MouseOverHandler;
import org.neatshell.gneat.client.handler.mouse.MouseUpHandler;
import org.neatshell.gneat.client.presenter.core.Presenter.IView.NodeType;
import org.neatshell.gneat.client.presenter.core.manipulation.css.Css;
import org.neatshell.gneat.client.tool.Strings;

public class Presenter implements IPresenter {
	public interface IView {
		public enum NodeType { ELEMENT, TEXT, COMMENT }
		IView clone();

		void append(IView view);
		void append(String html);
		void insertBefore(IView newView, IView refView);
		void insertBefore(String html, IView refView);

		void html(String html);
		String html();

		void text(String text);
		String text();

		void detach();
		void remove();

		IView  setNode(Object node);
		IView  setNode(NodeType type, String value);

		Object getNode();
		Object getNextNode();
		Object getPrevNode();
		Object getParentNode();
		Object getFirstChildNode();
		Object getLastChildNode();

//		boolean is(String... filters);

		void setAttribute(String name, String value);
		void setClassName(String className);
		void setCss(String name, String value);
		void setCss(Css css, String value);
		void setProperty(String propName, Object value);

		String getAttribute(String name);
		String getClassName();
		String getCss(String name);
		String getProperty(String propName);

		void removeProp(String propName);
		void removeAttr(String name);

		void addEventHandler(String event, Handler handler);
		void removeEventHandler(List<Handler> handlers);

		void trigger(String event, HashMap<String, List<Handler>> handlers);

		String getNodeName();
		short  getNodeType();
		String getNodeValue();

		void show();
	}

	private static int UID = 0;

	private IView _view;
	HashMap<String, List<Handler>>   _handlers;
	private HashMap<String, Object>  _data;
	
	protected Presenter(IView view) {
		_view = view;		
		//TODO how can avoid this waste of memory?
		PresenterCache.put(this);
	}

	public Presenter(String tagName) {
		this(GneatCore.getCoreInjector().getView().setNode(tagName));
	}

	public Presenter() {
		this("element");
	}

	public Presenter(NodeType nodeType, String nodeValue) {
		this(GneatCore.getCoreInjector().getView().setNode(nodeType, nodeValue));
	}

	@Override
	public IPresenterList add(IPresenter... presenters) {
		return new PresenterList(this).add(presenters);
	}

	@Override
	public IPresenterList add(IPresenterList list) {
		return new PresenterList(this).add(list);
	}

	@Override
	public IPresenter addClass(String... classNames) {
		if (is(1)) {
			String oldClass = getView().getClassName();
			if (oldClass == null) oldClass = "";
//			oldClass = Strings.trimExtra(oldClass);
			oldClass = oldClass.trim();
			String newClass = "";
			for (String className : Strings.cleanAndSplit(classNames)) {
				if (!oldClass.contains(className)) {
					newClass+=" "+className;
				}
			}
			getView().setClassName(oldClass+newClass);
		}
		return this;
	}

	@Override
	public IPresenter after(IPresenter... presenters) {
		if (parent().is()) {
			IPresenter next = next();
			if (next.is()) {
				for (IPresenter p : presenters) {
					parent().getView().insertBefore(p.getView(), next.getView());
				}
			}
			else parent().append(presenters);				
		}
		return this;
	}
	
	@Override
	public IPresenter after(IPresenterList list) {
		return list != null ? after(list.toArray()) : this;
	}
	
	@Override
	public IPresenter after(String... html) {
		if (parent().is() && html != null) {
			IPresenter next = next();
			if (next.is()) {
				for (String h : html) {
					if (Strings.exists(h)) {
						parent().getView().insertBefore(h, next.getView());
					}
				}
			}
			else parent().append(html);
		}
		return this;
	}

	@Override
	public IPresenter append(IPresenter... presenters) {
		if (presenters != null) {
			for (IPresenter p : presenters) {
				if (p != null && p.is()) {
					getView().append(p.getView());
				}
			}
		}
		return this;
	}

	@Override
	public IPresenter append(IPresenterList list) {
		return (list != null) ? append(list.toArray()) : this;
	}

	@Override
	public IPresenter appendTo(IPresenter target) {
		if (target != null && target.is()) target.append(this);
		return this;
	}

	@Override
	public IPresenter append(String... html) {
		if (html != null) {
			for (String h : html) {
				if (Strings.exists(h)) {
					getView().append(h);
				}
			}
		}
		return this;
	}

	@Override
	public String attr(Attribute attr) {
		if (is(1)) {			
			if (attr != null) {
				return attr(attr.getName());
			}
		}
		return null;
	}

	@Override
	public String attr(String attrName) {
		if (is(1)) {
			if (Strings.exists(attrName)) {
				return getView().getAttribute(attrName);
			}
		}
		return null;
	}
	
	@Override
	public IPresenter attr(String name, String value) {
		if (is(1)) {
			if (Strings.exists(name) && value != null) {
				getView().setAttribute(name, value);
			}
		}
		return this;
	}

	@Override
	public IPresenter attr(Attribute attr, String value) {		
	
		if (is(1)) {
			if (attr != null) {
				return attr(attr.getName(), value);
			}
		}
		return this;
	}
	
	@Override
	public IPresenter before(IPresenter... presenters) {
		if (parent().is() && presenters != null) {
			for (IPresenter p : presenters) {
				if (p != null && p.is()) {
					parent().getView().insertBefore(p.getView(), getView());					
				}
			}
		}
		return this;
	}

	@Override
	public IPresenter before(IPresenterList list) {
		return list != null ? before(list.toArray()) : this;
	}

	@Override
	public IPresenter before(String... html) {
		if (parent().is() && html != null) {
			for (String h : html) {
				if (Strings.exists(h)) {
					parent().getView().insertBefore(h, getView());
				}
			}
		}
		return this;
	}
	
	@Override
	public IPresenter blur(BlurHandler handler) {
		return on(IBlurEvent.NAME, handler);
	}
	
	@Override
	public IPresenter blur() {
		return trigger(IBlurEvent.NAME);
	}
		
	@Override
	public IPresenter change(ChangeHandler handler) {
		return on(IChangeEvent.NAME, handler);
	}
	
	@Override
	public IPresenter change() {
		return trigger(IChangeEvent.NAME);
	}

	@Override
	public IPresenter child(Integer index, Integer nodeType) {
		IPresenter child = PresenterNull.get();
		if (index != null) {
			if (index >= 0) {
				child = PresenterCache.get(getView().getFirstChildNode());
				while (index >= 0 && child.is()) {
					if (nodeType == null || child.is(nodeType)) index--;
					if (index >= 0) child = child.next(null);
				}
			}
			else {
				child = PresenterCache.get(getView().getLastChildNode());
				while (index < 0 && child.is()) {
					if (nodeType == null || child.is(nodeType)) index++;
					if (index < 0) child = child.prev(null);
				}
			}
		}
		return child;		
	}

	@Override
	public IPresenter child(Integer index) {
		return child(index, 1);
	}

	@Override
	public IPresenterList children() {
		return children(1);
	}
	
	@Override
	public IPresenterList children(Integer nodeType) {
		IPresenter child = child(0, nodeType);
		IPresenterList children = new PresenterList();
		while (child.is()) {
			children.add(child);
			child = child.next(nodeType);
		}
		return children;
	}

//	@Override
//	public IPresenterList children(String... filters) {
//		return children().filter(filters);
//	}
	
	@Override
	public IPresenter click(ClickHandler handler) {
		return on(IClickEvent.NAME, handler);
	}

	@Override
	public IPresenter click() {
		return trigger(IClickEvent.NAME);
	}

	public IPresenter clone() {
		return clone(false, false);
	}

	@Override
	public IPresenter clone(Boolean withDataAndEvents) {
		return clone(new Presenter(getView().clone()), withDataAndEvents, false);
	}

	@Override
	public IPresenter clone(Boolean withDataAndEvents, Boolean deepWithDataAndEvents) {
		return clone(new Presenter(getView().clone()), withDataAndEvents, deepWithDataAndEvents);
	}
	
	protected IPresenter clone(IPresenter cloned, Boolean withDataAndEvents, Boolean deepWithDataAndEvents) {
		IPresenterList children = children(null);
		if (is(1) && withDataAndEvents != null && withDataAndEvents) {
			cloneData(cloned);
			cloneHandlers(cloned);			
			return cloned.append(children.clone(deepWithDataAndEvents, deepWithDataAndEvents));
		}
		return cloned.append(children.clone(false, false));
	}

	void cloneData(IPresenter cloned) {
		if (_data != null) {
			Iterator<String> it_data   = _data.keySet().iterator();
			//cloning data
			while (it_data.hasNext()) {
				String key = (String) it_data.next();
				cloned.data(key, _data.get(key));
			}
		}
	}
	
	void cloneHandlers(IPresenter cloned) {
		if (_handlers != null) {
			Iterator<String> it_events = _handlers.keySet().iterator();
			while (it_events.hasNext()) {
				String event = it_events.next();
				List<Handler> handlers = _handlers.get(event);
				Iterator<Handler> it_handlers = handlers.iterator();
				while (it_handlers.hasNext()) {
					((Presenter) cloned).on(event, it_handlers.next().clone());
				}
			}
		}		
	}
	
	@Override
	public IPresenter content(Integer index) {
		IPresenter content = child(index, null);
		while (content.is() && !(content.is(1) || content.is(3) || content.is(8))) {
			content = content.next(null);
		}
		return content;
	}
		
	@Override
	public IPresenterList contents() {
		IPresenterList contents = new PresenterList();
		IPresenter content = child(0, null);
		while (content.is()) {
			if (content.is(1) || content.is(3) || content.is(8)) {
				contents.add(content);
			}
			content = content.next(null);
		}
		return contents;
	}

	@Override
	public String css(Css css) {
		if (is(1) && css != null) {
			return css(css.getName());
		}
		return null;
	}

	@Override
	public String css(String propName) {
		if (is(1) && Strings.exists(propName)) {
			return getView().getCss(propName);
		}
		return null;
	}

	@Override
	public IPresenter css(Css css, String value) {
		if (is(1) && css != null) {
			css(css.getName(), value);
		}		
		return this;
	}

	@Override
	public IPresenter css(String name, String value) {
		if (is(1) && Strings.exists(name) && value != null) {
			getView().setCss(name, value);
		}
		return this;
	}

	@Override
	public HashMap<String, Object> data() {
		return _data;
	}
	
	@Override
	public Object data(String key) {
		return _data != null ? _data.get(key) : null;
	}
	
	@Override
	public IPresenter data(String key, Object value) {		
		if (is(1)) {
			if (_data == null) _data = new HashMap<String, Object>();
			_data.put(key, value);			
		}
		return this;
	}

	@Override
	public IPresenter dblclick(DblClickHandler handler) {
		return on(IDblClickEvent.NAME, handler);
	}

	@Override
	public IPresenter dblclick() {
		return trigger(IDblClickEvent.NAME);
	}
	
	@Override
	public IPresenter detach() {		
		_view.detach();
		return this;
	}

	@Override
	public IPresenter empty() {
		if (is(1)) {
			children(null).remove();
			text("");
		}
		return this;
	}

	@Override
	public IPresenter error(ErrorHandler handler) {
		return on(IErrorEvent.NAME, handler);
	}

	@Override
	public IPresenter error() {
		return trigger(IErrorEvent.NAME);
	}
	
	@Override
	public IPresenterList find() {
		return find(1);
	}
	
	@Override
	public IPresenterList find(Integer nodeType) {		
		IPresenterList find = new PresenterList();
		
		IPresenterList children = children(null);
		
		for (IPresenter child : children.toArray()) {
			if (child.is(nodeType)) find.add(child);
			if (child.is(1)) {
				find.add(child.find(nodeType));
			}
		}
		return find;
	}
	
//	@Override
//	public IPresenterList find(String... filters) {
//		return find().filter(filters);
//	}
		
	@Override
	public IPresenter focus(FocusHandler handler) {
		return on(IFocusEvent.NAME, handler);
	}
	
	@Override
	public IPresenter focus() {
		return trigger(IFocusEvent.NAME);
	}	
	
	/**
	 * Get this Presenter's View
	 * @return the view associated with this presenter
	 */
	public IView getView() { 
		return _view; 
	}

	@Override
	public boolean has(IPresenter... contained) {
		if (is(1)) return find(null).is(contained);
		return false;
	}
	
	@Override
	public boolean has(IPresenterList list) {
		return (list != null) ? has(list.toArray()) : false;
	}

	@Override
	public boolean hasAttr(Attribute... attrs) {
		if (is(1)) {
			if (attrs == null) return false;
			for (Attribute attr : attrs) {
				if (attr(attr) == null) return false;
			}
			return true;
		}
		return false;
	}
	
	@Override
	public boolean hasAttr(String... attrNames) {
		if (is(1)) {			
			if (attrNames == null) return false;
			for (String attrName : attrNames) {
				if (attr(attrName) == null) return false;
			}
			return true;			
		}
		return false;
	}
	
	@Override
	public boolean hasClass(String... classNames) {
		if (is(1)) {
			String oldClassName = getView().getClassName();			
			if (!Strings.exists(oldClassName)) return false;
//			oldClassName = Strings.trimExtra(oldClassName);
			oldClassName = oldClassName.trim();		
			for (String className : classNames) {
//				if (!oldClassName.contains(Strings.trimExtra(className))) 
				if (!oldClassName.contains(className.trim())) 				
					return false;
			}
			return true;
		}
		return false;		
	}
	
	@Override
	public boolean hasProp(Property... props) {
		if (is(1)) {
			if (props == null) return false;
			for (Property prop : props) {
				if (prop(prop) == null) return false;
			}
			return true;
		}
		return false;
	}	

	@Override
	public boolean hasProp(String... propNames) {
		if (is(1)) {
			if (propNames == null) return false;
			for (String propName : propNames) {
				if (prop(propName) == null) return false;
			}
			return true;			
		}
		return false;
	}
	
	@Override
	public IPresenter height(String value) {
		if (is(1)) {
			return css(Css.HEIGHT, value);
		}
		return this;
	}

	@Override
	public IPresenter hide() {
		return css(Css.DISPLAY, "none");
	}

	@Override
	public String html() {
		if (is(1)) return getView().html();
		else return null;
	}

	@Override
	public IPresenter html(String html) {
		if (Strings.exists(html)) {
			getView().html(html);
		}
		return this;
	}
	
	@Override
	public IPresenter keydown(KeyDownHandler handler) {
		return on(IKeyDownEvent.NAME, handler);
	}

	@Override
	public IPresenter keydown() {
		return trigger(IKeyDownEvent.NAME);
	}

	@Override
	public IPresenter keypress(KeyPressHandler handler) {
		return on(IKeyPressEvent.NAME, handler);
	}
	
	@Override
	public IPresenter keypress() {
		return trigger(IKeyPressEvent.NAME);
	}
	
	@Override
	public IPresenter keyup(KeyUpHandler handler) {
		return on(IKeyUpEvent.NAME, handler);
	}
	
	@Override
	public IPresenter keyup() {
		return trigger(IKeyUpEvent.NAME);
	}
		
	@Override
	public String id() {
		return id(false);
	}

	@Override
	public String id(boolean auto) {
		if (attr(Attribute.ID) != null) return attr(Attribute.ID);
		return auto ? id("gn-pid"+UID++).id() : null;
	}

	@Override
	public IPresenter id(String id) {
		return attr(Attribute.ID, id);
	}

	@Override
	public IPresenter insertAfter(IPresenter target) {
		if (target != null && target.is()) {
			target.after(this);
		}
		return this;
	}

	@Override
	public IPresenter insertBefore(IPresenter target) {
		if (target != null && target.is()) {
			target.before(this);
		}
		return this;
	}

	@Override
	public boolean is() {
		return true;
	}
	
	@Override
	public boolean is(IPresenter... presenters) {
		if (presenters != null) {
			for (IPresenter p : presenters)	{
				// if at least one is equals -> exit with true
				if (p != null
					&& p.is()
					&& p.equals(this)
				) return true;
			}
		}
		//otherwise exit with false
		return false;			
	}
	
	@Override
	public boolean is(Integer nodeType) {
		if (nodeType == null) return true;		
		else {
			return getView().getNodeType() == nodeType;
		}
	}

	@Override
	public boolean is(IPresenterList list) {
		return list != null ? is(list.toArray()) : false;
	}

//	@Override
//	public boolean is(String... filters) {
//		return getView().is(filters);
//	}
	
	@Override
	public IPresenter load(LoadHandler handler) {
		return on(ILoadEvent.NAME, handler);
	}
	
	@Override
	public IPresenter load() {
		return trigger(ILoadEvent.NAME);
	}
	
	@Override
	public IPresenter mousedown(MouseDownHandler handler) {
		return on(IMouseDownEvent.NAME, handler);
	}
	
	@Override
	public IPresenter mousedown() {
		return trigger(IMouseDownEvent.NAME);
	}

	@Override
	public IPresenter mousemove(MouseMoveHandler handler) {
		return on(IMouseMoveEvent.NAME, handler);
	}
	
	@Override
	public IPresenter mousemove() {
		return trigger(IMouseMoveEvent.NAME);
	}

	@Override
	public IPresenter mouseout(MouseOutHandler handler) {
		return on(IMouseOutEvent.NAME, handler);
	}
	
	@Override
	public IPresenter mouseout() { 
		return trigger(IMouseOutEvent.NAME); 
	}

	@Override
	public IPresenter mouseover(MouseOverHandler handler) {
		return on(IMouseOverEvent.NAME, handler);
	}

	@Override
	public IPresenter mouseover() {
		return trigger(IMouseOverEvent.NAME);
	}
	
	@Override
	public IPresenter mouseup(MouseUpHandler handler) {
		return on(IMouseUpEvent.NAME, handler);
	}

	@Override
	public IPresenter mouseup() {
		return trigger(IMouseUpEvent.NAME);
	}
	
	@Override
	public IPresenter next(Integer nodeType) {
		IPresenter next = PresenterCache.get(getView().getNextNode());
		while (next.is()) {
			if (nodeType == null || next.is(nodeType)) return next;
			else {
				next = next.next(nodeType);
			}
		}
		return next;
	}
	
	@Override
	public IPresenter next() {
		return next(1);
	}
	
	@Override
	public IPresenterList nextAll() {
		return nextAll(1);
	}

	@Override
	public IPresenterList nextAll(Integer nodeType) {
		IPresenterList nextAll = new PresenterList();
		IPresenter next = next(nodeType);
		while (next.is()) {
			nextAll.add(next);
			next = next.next(nodeType);
		}
		return nextAll;
	}

	@Override
	public IPresenterList nextUntil(IPresenter until) {
		return nextUntil(until, 1);
	}

	@Override
	public IPresenterList nextUntil(IPresenter until, Integer nodeType) {
		if (until != null && until.is()) {
			if (parent() == until.parent()) {
				IPresenterList nextUntil = new PresenterList();
				IPresenter next = next(nodeType);
				while (next.is() && !next.is(until)) {
					nextUntil.add(next);
					next = next.next(nodeType);
				}
				return nextUntil;
			} else return nextAll(nodeType);			
		} else return nextAll(nodeType);
	}

	@Override
	public IPresenter off(String event) {
		if (_handlers != null) {
			getView().removeEventHandler(_handlers.get(event));
			_handlers.remove(event);
		}
		return this;
	}

	@Override
	public IPresenter on(CustomHandler handler) {
		return  on(handler.getHandledEvent(), (Handler) handler);
	}

	IPresenter on(String event, Handler handler) {
		if (is(1)) {
			handler.current(this);

			if (_handlers == null) _handlers = new HashMap<String, List<Handler>>();
			
			List<Handler> handlersList = _handlers.get(event);
			if (handlersList == null) handlersList = new ArrayList<Handler>();

			handlersList.add(handler);
				
			getView().addEventHandler(event, handler);	
			_handlers.put(event, handlersList);
		}
		return this;
	}
	
	@Override
	public IPresenter parent() {
		return PresenterCache.get(getView().getParentNode());
	}
		
	@Override
	public IPresenterList parents() {
		IPresenterList parents = new PresenterList();
		IPresenter parent = parent();

		while (parent.is()) {
			parents.add(parent);
			parent = parent.parent();
		}
		return parents;
	}

	@Override
	public IPresenterList parentsUntil(IPresenter until) {
		IPresenterList parents = new PresenterList();
		IPresenter parent = parent();
		
		while (parent.is() && !parent.is(until)) {
			parents.add(parent);
			parent = parent.parent();
		}
		return parents;
	}
	
	@Override
	public IPresenter prepend(IPresenter... presenters) {
		IPresenter first = child(0, null);
		if (first.is()) {
			first.before(presenters);
		}
		else {
			append(presenters);
		}
		return this;
	}
	
	@Override
	public IPresenter prepend(IPresenterList list) {
		return (list != null) ? prepend(list.toArray()) : this;		
	}

	@Override
	public IPresenter prependTo(IPresenter target) {		
		if (target != null && target.is()) target.prepend(this);
		return this;
	}

	@Override
	public IPresenter prepend(String... html) {
		IPresenter first = child(0, null);
		if (first.is()) {
			first.before(html);
		}
		else {
			append(html);
		}
		return this;
	}

	@Override
	public IPresenter prev() {
		return prev(1);
	}

	@Override
	public IPresenter prev(Integer nodeType) {
		IPresenter prev = PresenterCache.get(getView().getPrevNode());
		while (prev.is()) {
			if (nodeType == null || prev.is(nodeType)) {
				return prev;
			}
			else {
				prev = prev.prev(nodeType);
			}
		}
		return prev;		
	}

	@Override
	public IPresenterList prevAll() {
		return prevAll(1);
	}

	@Override
	public IPresenterList prevAll(Integer nodeType) {
		IPresenterList prevAll = new PresenterList();
		IPresenter prev = prev(nodeType);
		while (prev.is()) {
			prevAll.add(prev);
			prev = prev.prev(nodeType);
		}
		return prevAll;
	}
	
	@Override
	public IPresenterList prevUntil(IPresenter until) {
		return prevUntil(until, 1);
	}
	
	@Override
	public IPresenterList prevUntil(IPresenter until, Integer nodeType) {
		if (until != null && until.is()) {
			if (parent() == until.parent()) {
				IPresenterList prevUntil = new PresenterList();
				IPresenter prev = prev(nodeType);
				while (prev.is() && !prev.is(until)) {
					prevUntil.add(prev);
					prev = prev.prev(nodeType);
				}
				return prevUntil;
			} else return prevAll(nodeType);
		} else return prevAll(nodeType);
	}
	
	@Override
	public String prop(Property prop) {
		if (is(1)) {
			if (prop != null) return prop(prop.getName());			
		}
		return null;
	}
	
	@Override
	public String prop(String propName) {
		if (is(1)) {
			if (propName != null && !propName.isEmpty()) {
				return getView().getProperty(propName);
			}	
		}			
		return null;
	}

	@Override
	public IPresenter prop(Property prop, String value) {
		if (is(1) && prop != null) {
			return prop(prop.getName(), value);
		}
		return this;
	}

	@Override
	public IPresenter prop(String propName, String propValue) {
		if (is(1)) {
			if (propName != null && !propName.isEmpty() && propValue != null) {
				getView().setProperty(propName, propValue);
			}			
		}		
		return this;
	}
	
	@Override
	public IPresenter remove() {
		children(null).remove();
		_view.remove();
		if (_handlers != null) {
			Iterator<String> it_events = _handlers.keySet().iterator();
			while (it_events.hasNext()) {
				_view.removeEventHandler(_handlers.get(it_events.next()));
			}
			_handlers.clear();
			_handlers = null;
		}
		if (_data != null) {
			_data.clear();
			_data = null;
		}
		_view = null;
		return PresenterCache.remove(this);
	}

	@Override
	public IPresenter removeAttr(Attribute... attrs) {
		if (is(1) && attrs != null) {
			for (Attribute attr : attrs) {
				if (attr != null) {
					for (String a : Strings.cleanAndSplit(attr.getName())) {
						getView().removeAttr(a);
					}
				}
			}
		}
		return this;
	}
	
	@Override
	public IPresenter removeAttr(String... attrNames) {
		if (is(1)) {
			for (String attrName : Strings.cleanAndSplit(attrNames)) {					
				getView().removeAttr(attrName);	
			}
		}
		return this;
	}

	@Override
	public IPresenter removeClass(String... classNames) {
		if (is(1)) {
			String oldClass = getView().getClassName();
			if (!Strings.exists(oldClass)) return this;
//			oldClass = Strings.trimExtra(oldClass);
			oldClass = oldClass.trim();		
			String newClass = oldClass;

			for (String className : Strings.cleanAndSplit(classNames)) {
				newClass = newClass.replace(className, "");
			}
			if (!newClass.equals(oldClass)) {
				getView().setClassName(newClass);				
			}
		}
		return this;
	}

	@Override
	public IPresenter removeProp(Property... props) {
		if (is(1) && props != null) {
			for (Property prop : props) {
				if (prop != null) {
					getView().removeProp(prop.getName());				
				}
			}
		}
		return this;
	}
	
	@Override
	public IPresenter removeProp(String... propNames) {
		if (is(1)) {
			for (String propName : Strings.cleanAndSplit(propNames)) {
				getView().removeProp(propName);
			}
		}
		return this;
	}

	@Override
	public IPresenter replaceClass(String oldClassName, String newClassName) {
		if (is(1)) {
			if (Strings.exists(oldClassName) && newClassName != null) {
				String oldClass = getView().getClassName();
				if (!Strings.exists(oldClass)) return this;
//				oldClass = Strings.trimExtra(oldClass);
				oldClass = oldClass.trim();
				String newClass = oldClass;

				newClass = newClass.replace(oldClassName, newClassName);
				if (!newClass.equals(oldClass)) {
					getView().setClassName(newClass);
				}
			}
		}
		return this;
	}

//	@Override
//	public void replaceWith(IPresenter... presenter) {
//		after(presenter);
//	}
//
//	@Override
//	public void replaceWith(IPresenterList list) {
//		after(list);
//	}
	
	@Override
	public IPresenter resetClass() {
		if (is(1)) getView().setClassName("");
		return this;
	}
	
	@Override
	public IPresenter scroll(ScrollHandler handler) {
		return on(IScrollEvent.NAME, handler);
	}
	
	@Override
	public IPresenter scroll() {
		return trigger(IScrollEvent.NAME);
	}

	@Override
	public IPresenter shallow() {
		return shallow(false);
	}

	@Override
	public IPresenter shallow(Boolean withDataAndEvents) {
		return shallow(new Presenter(getView().clone()), withDataAndEvents);
	}

	protected IPresenter shallow(IPresenter cloned, Boolean withDataAndEvents) {
		if (is(1) && withDataAndEvents != null && withDataAndEvents) {
			cloneData(cloned);
			cloneHandlers(cloned);
		}
		return cloned;
	}

	@Override
	public IPresenter show() {
		if (is(1)) getView().show();
		return this;
	}

	@Override
	public IPresenterList siblings() {
		return siblings(1);
	}

	@Override
	public IPresenterList siblings(Integer nodeType) {
		return prevAll(nodeType).reverse().add(nextAll(nodeType));
	}
		
	@Override
	public IPresenter submit(SubmitHandler handler) {
		return on(ISubmitEvent.NAME, handler);
	}
	
	@Override
	public IPresenter submit() {
		return trigger(ISubmitEvent.NAME);
	}
	
	@Override
	public String text() {
		if (is(1)) return getView().text();			
		return null;
	}

	@Override
	public IPresenter text(String text) {
		if (is(1)) getView().text(text);			
		return this;
	}

	//FIXME toggle() test this!!!!
	@Override
	public IPresenter toggle() {
		return css(Css.DISPLAY).equals("none") ? show() : hide();
	}

	@Override
	public IPresenter toggleClass(String... classNames) {
		if (is(1) && classNames != null) {
			for (String className : classNames) {
				if (hasClass(className)) {
					removeClass(className);
				}
				else {
					addClass(className);
				}
			}
		}
		return this;
	}

	@Override
	public IPresenter trigger(String event) {
		if (is(1) && Strings.exists(event)) getView().trigger(event, _handlers);
		return this;
	}

	@Override
	public IPresenter unwrap() {
		IPresenter wrapper = parent();
		//not check for not existence of parent because if so, PresenterNull will be provided
		//and the following instruction will be no-ops
		wrapper.after(siblings(null).add(this).detach());
		wrapper.remove();
		return this;
	}

	@Override
	public IPresenter val(String value) {
		return prop(Property.VALUE, value);
	}
	
	@Override
	public String val() {
		return prop(Property.VALUE);
	}
	
	@Override
	public IPresenter width(String value) {
		return css(Css.WIDTH, value);
	}

	@Override
	public IPresenter wrap(IPresenter wrapper) {
		if (parent().is()) wrapper.insertAfter(this);
		if (wrapper != null && wrapper.is()) {
			IPresenter target = wrapper;
			while (target.child(0).is()) {
				target = target.child(0);
			}
			appendTo(target);
		}
		return this;
	}

	@Override
	public IPresenter wrap(CallbackPresenter callback) {
		return wrap(callback.fire(this));
	}

	@Override
	public IPresenter wrap(String html) {
		return wrap(new Presenter().append(html).child(0, 1));
	}
	
	@Override
	public IPresenter wrap(CallbackString callback) {
		return wrap(callback.fire(this));
	}

//	@Override
//	public IPresenter wrapInner(IPresenter wrapper) {
//		return null;
//	}
//
//	@Override
//	public IPresenter wrapInner(String html) {
//		return null;
//	}
}